package com.course.web.biz;

import com.course.domain.enums.TaskType;
import com.course.domain.model.*;
import com.course.domain.service.*;
import com.course.util.DateUtils;
import com.course.web.model.ClassItemVM;
import com.course.web.model.ClassMarkVM;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;

/**
 * Created by Justin on 2017/6/14.
 */
@Component
public class WebClassBiz {

    private final TaskService taskService;
    private final ClassService classService;
    private final CourseService courseService;
    private final AgendaService agendaService;
    private final UserService userService;

    public WebClassBiz(TaskService taskService,
                       ClassService classService,
                       CourseService courseService,
                       AgendaService agendaService,
                       UserService userService) {
        this.taskService = taskService;
        this.classService = classService;
        this.courseService = courseService;
        this.agendaService = agendaService;
        this.userService = userService;
    }

    public ClassItemVM createItem(ClassItemVM vm) {
        ClassPlan plan = classService.createPlan(toPlan(vm));
        vm.setId(plan.getId());
        return vm;
    }

    public ClassItemVM updateItem(ClassItemVM vm) {
        classService.updatePlan(toPlan(vm));
        return vm;
    }

    public ClassItemVM deleteItem(ClassItemVM vm) {
        classService.deletePlan(toPlan(vm));
        return vm;
    }

    public ClassItemVM loadItem(int itemId) {
        ClassPlan classPlan = classService.loadPlan(itemId);
        Student student = userService.loadStudent(classPlan.getStudentId());
        Teacher teacher = userService.loadTeacher(classPlan.getTeacherId());
        TaskClass task = taskService.loadClass(classPlan.getClassId());
        return transform(classPlan, student, teacher, task);
    }

    public ClassMarkVM updateMark(ClassMarkVM vm) {
        ClassMark mark = new ClassMark();
        mark.setMark1(vm.getMark1());
        mark.setMark2(vm.getMark2());
        mark.setMark3(vm.getMark3());
        mark.setMark4(vm.getMark4());
        mark.setMark5(vm.getMark5());
        mark.setSuggest(vm.getSuggest());
        mark.setComment(vm.getComment());
        mark.setMessage(vm.getMessage());
        mark.setSugCn(vm.getSugCn());
        mark.setComCn(vm.getComCn());
        Gson gson = new Gson();
        classService.updateMark(vm.getId(), gson.toJson(mark));
        return vm;
    }

    public List<ClassItemVM> findItemByStudent(int studentId) {
        Student student = userService.loadStudent(studentId);
        int courseId = student.getCourseId();
        CourseInfo courseInfo = courseService.loadInfo(courseId);
        AgendaInfo agendaInfo = agendaService.loadInfo(studentId, courseId);
        if (courseInfo == null || agendaInfo == null) {
            return Collections.emptyList();
        }
        int agendaId = agendaInfo.getId();
        List<ClassPlan> items = classService.findPlanByStudentIdAndCourseId(studentId, courseId);
        List<TaskClass> tasks = taskService.findClass(courseId);
        List<CoursePlan> plans = courseService.findPlanByType(courseId, TaskType.CLASS);
        List<AgendaWeek> weeks = agendaService.findWeek(agendaId);
        List<Integer> teacherIds = Lists.transform(items, ClassPlan::getTeacherId);
        Map<Integer, TaskClass> taskMap = Maps.uniqueIndex(tasks, TaskClass::getId);
        Map<Integer, AgendaWeek> weekMap = Maps.uniqueIndex(weeks, AgendaWeek::getPartIdx);
        Map<Integer, ClassPlan> classMap = Maps.uniqueIndex(items, ClassPlan::getPlanId);
        Map<Integer, Student> studentMap = Collections.singletonMap(studentId, student);
        Map<Integer, Teacher> teacherMap = Maps.uniqueIndex(userService.findTeacher(teacherIds), Teacher::getId);
        Comparator<CoursePlan> comparator = Comparator
                .comparing(CoursePlan::getWeekIdx)
                .thenComparing(CoursePlan::getWeekDay)
                .thenComparing(CoursePlan::getPartIdx);
        plans.sort(comparator);
        Date start = courseInfo.getBeginDate();
        List<ClassItemVM> vms = Lists.newArrayListWithCapacity(plans.size());
        for (CoursePlan plan : plans) {
            TaskClass task = taskMap.get(plan.getTaskId());
            AgendaWeek week = weekMap.get(plan.getPartIdx());
            if (task == null || week == null) {
                continue;
            }
            ClassPlan classPlan = classMap.get(plan.getId());
            if (classPlan == null) {
                Date date = toDate(start, plan, week);
                if (date == null) {
                    continue;
                }
                classPlan = new ClassPlan();
                classPlan.setId(0);
                classPlan.setStudentId(studentId);
                classPlan.setCourseId(courseId);
                classPlan.setClassId(plan.getTaskId());
                classPlan.setPlanId(plan.getId());
                classPlan.setDate(date);
                classPlan.setRoom("");
                classPlan.setMark("{}");
            }
            ClassItemVM vm = transform(classPlan, studentMap, teacherMap, taskMap);
            vms.add(vm);
        }
        return vms;
    }

    public List<ClassItemVM> findItemByTeacher(int teacherId) {
        Teacher teacher = userService.loadTeacher(teacherId);
        List<ClassPlan> items = classService.findPlanByTeacherId(teacherId);
        List<Integer> taskIds = Lists.transform(items, ClassPlan::getClassId);
        List<Integer> studentIds = Lists.transform(items, ClassPlan::getStudentId);
        Map<Integer, TaskClass> taskMap = Maps.uniqueIndex(taskService.findClass(taskIds), TaskClass::getId);
        Map<Integer, Student> studentMap = Maps.uniqueIndex(userService.findStudent(studentIds), Student::getId);
        Map<Integer, Teacher> teacherMap = Collections.singletonMap(teacherId, teacher);
        return Lists.transform(items, plan -> transform(plan, studentMap, teacherMap, taskMap));
    }

    public List<ClassMarkVM> findMarkByStudent(int studentId) {
        Student student = userService.loadStudent(studentId);
        int courseId = student.getCourseId();
        List<ClassPlan> items = classService.findMarkByStudentIdAndCourseId(studentId, courseId);
        List<Integer> taskIds = Lists.transform(items, ClassPlan::getClassId);
        List<Integer> teacherIds = Lists.transform(items, ClassPlan::getTeacherId);
        Map<Integer, TaskClass> taskMap = Maps.uniqueIndex(taskService.findClass(taskIds), TaskClass::getId);
        Map<Integer, Teacher> teacherMap = Maps.uniqueIndex(userService.findTeacher(teacherIds), Teacher::getId);
        List<ClassMarkVM> vms = Lists.newArrayListWithCapacity(items.size());
        for (ClassPlan classPlan : items) {
            Teacher teacher = teacherMap.get(classPlan.getTeacherId());
            TaskClass task = taskMap.get(classPlan.getClassId());
            ClassMarkVM vm = transformMark(classPlan, student, teacher, task);
            vms.add(vm);
        }
        return vms;
    }

    public ClassItemVM findNextItemByStudent(int studentId) {
        Student student = userService.loadStudent(studentId);
        int courseId = student.getCourseId();
        ClassPlan classPlan = classService.findNextPlanByStudentIdAndCourseId(studentId, courseId);
        if (classPlan == null) {
            return null;
        }
        int classId = classPlan.getClassId();
        int teacherId = classPlan.getTeacherId();
        TaskClass task = taskService.loadClass(classId);
        Teacher teacher = userService.loadTeacher(teacherId);
        return transform(classPlan, student, teacher, task);
    }

    public ClassItemVM findNextItemByTeacher(int teacherId) {
        ClassPlan classPlan = classService.findNextPlanByTeacherId(teacherId);
        if (classPlan == null) {
            return null;
        }
        int classId = classPlan.getClassId();
        int studentId = classPlan.getStudentId();
        TaskClass task = taskService.loadClass(classId);
        Student student = userService.loadStudent(studentId);
        Teacher teacher = userService.loadTeacher(teacherId);
        return transform(classPlan, student, teacher, task);
    }

    private ClassItemVM transform(ClassPlan classPlan, Student student, Teacher teacher, TaskClass task) {
        ClassItemVM vm = new ClassItemVM();
        vm.setId(classPlan.getId());
        vm.setStudentId(classPlan.getStudentId());
        vm.setTeacherId(classPlan.getTeacherId());
        vm.setCourseId(classPlan.getCourseId());
        vm.setTaskId(classPlan.getClassId());
        vm.setPlanId(classPlan.getPlanId());
        vm.setDateTime(classPlan.getDate());
        vm.setRoom(classPlan.getRoom());
        vm.setTask(task != null ? task.getTitle() : "");
        vm.setTeacher(teacher != null ? teacher.getName() : "");
        vm.setStudent(student != null ? student.getName() : "");
        vm.setLink(task != null ? task.getPptLink() : "");
        return vm;
    }

    private ClassItemVM transform(ClassPlan classPlan,
                                  Map<Integer, Student> studentMap,
                                  Map<Integer, Teacher> teacherMap,
                                  Map<Integer, TaskClass> taskMap) {
        ClassItemVM vm = new ClassItemVM();
        TaskClass task = taskMap.get(classPlan.getClassId());
        Student student = studentMap.get(classPlan.getStudentId());
        Teacher teacher = teacherMap.get(classPlan.getTeacherId());
        vm.setId(classPlan.getId());
        vm.setStudentId(classPlan.getStudentId());
        vm.setTeacherId(classPlan.getTeacherId());
        vm.setCourseId(classPlan.getCourseId());
        vm.setTaskId(classPlan.getClassId());
        vm.setPlanId(classPlan.getPlanId());
        vm.setDateTime(classPlan.getDate());
        vm.setTask(task != null ? task.getTitle() : "");
        vm.setRoom(classPlan.getRoom());
        vm.setTeacher(teacher != null ? teacher.getName() : "");
        vm.setStudent(student != null ? student.getName() : "");
        vm.setLink(task != null ? task.getPptLink() : "");
        vm.syncTime();
        return vm;
    }

    private ClassPlan toPlan(ClassItemVM vm) {
        vm.syncDate();
        ClassPlan plan = new ClassPlan();
        plan.setId(vm.getId());
        plan.setStudentId(vm.getStudentId());
        plan.setTeacherId(vm.getTeacherId());
        plan.setCourseId(vm.getCourseId());
        plan.setClassId(vm.getTaskId());
        plan.setPlanId(vm.getPlanId());
        plan.setDate(vm.getDateTime());
        plan.setRoom(vm.getRoom());
        plan.setMark(new Gson().toJson(new ClassMark()));
        plan.setStatus(1);
        return plan;
    }

    private ClassMarkVM transformMark(ClassPlan classPlan, Student student, Teacher teacher, TaskClass task) {
        ClassMarkVM vm = new ClassMarkVM();
        vm.setId(classPlan.getId());
        vm.setDate(classPlan.getDate());
        Gson gson = new Gson();
        ClassMark mark = gson.fromJson(classPlan.getMark(), ClassMark.class);
        vm.setMark1(mark.getMark1());
        vm.setMark2(mark.getMark2());
        vm.setMark3(mark.getMark3());
        vm.setMark4(mark.getMark4());
        vm.setMark5(mark.getMark5());
        vm.setSuggest(mark.getSuggest());
        vm.setComment(mark.getComment());
        vm.setMessage(mark.getMessage());
        vm.setSugCn(mark.getSugCn());
        vm.setComCn(mark.getComCn());
        vm.setTeacher(teacher != null ? teacher.getName() : "");
        vm.setStudent(student != null ? student.getName() : "");
        vm.setTask(task != null ? task.getTitle() : "");
        return vm;
    }

    private Date toDate(Date begin, CoursePlan plan, AgendaWeek week) {
        int weekIdx = plan.getWeekIdx();
        int weekDay = plan.getWeekDay();
        int time = week.getByWeekDay(weekDay);
        if (time <= 0) {
            return null;
        }
        int offset = (weekIdx - 1) * 7 + weekDay - 1;
        LocalDateTime dateTime = DateUtils.toLocalDateTime(begin).plusDays(offset);
        int year = dateTime.getYear();
        int month = dateTime.getMonthValue();
        int day = dateTime.getDayOfMonth();
        int hour = time / 100;
        int minute = time - hour * 100;
        return DateUtils.toDate(year, month, day, hour, minute);
    }

}
